use crate::db::entities::role::{self};
use crate::db::entities::user_role::{CREATE_QUERY, Column, Entity, Model, Relation, TABLE_COLUMNS, TABLE_NAME, UserRoleActiveModel, UserRoleModel};
use anyhow::anyhow;
use anyhow::{Ok, Result};
use log::info;
use rbase::db::{self, get_connection};
use rbase::id_utils::gen_id;
use rbase::time_utils::get_systemtime_millis;
use sea_orm::ActiveValue::Set;
use sea_orm::ColumnTrait;
use sea_orm::QueryFilter;
use sea_orm::QuerySelect;
use sea_orm::RelationTrait;
use sea_orm::{EntityTrait, JoinType};

pub async fn init_tables() -> Result<()> {
    info!("Initializing tables");
    db::init_tables(TABLE_NAME, CREATE_QUERY, &TABLE_COLUMNS).await?;
    Ok(())
}

pub async fn insert_role(model: UserRoleModel) -> Result<UserRoleModel> {
    let connection = get_connection()?;

    let mut active_model = UserRoleActiveModel::from(model);

    let id = gen_id()?;
    active_model.id = Set(id);

    let systemtime = get_systemtime_millis();
    active_model.created_at = Set(Some(systemtime));
    active_model.modified_at = Set(Some(systemtime));

    Entity::insert(active_model).exec(&connection).await?;
    let model = find_by_id(id).await?.ok_or_else(|| anyhow!("Failed to insert user role"))?;
    Ok(model)
}

pub async fn find_by_user_id_and_role_id(user_id: i64, role_id: i64) -> Result<Option<Model>> {
    let connection = get_connection()?;

    Entity::find()
        .filter(Column::UserId.eq(user_id))
        .filter(Column::RoleId.eq(role_id))
        .one(&connection)
        .await
        .map_err(|e| anyhow!("Error finding user role: {:?}", e))
}

pub async fn find_by_user_id(user_id: i64) -> Result<Vec<String>> {
    let connection = get_connection()?;

    let raw_results = Entity::find()
        .column(role::Column::Role)
        .join(JoinType::InnerJoin, Relation::Role.def())
        .filter(Column::UserId.eq(user_id))
        .into_json()
        .all(&connection)
        .await?;

    let roles: Vec<String> = raw_results
        .into_iter()
        .filter_map(|json| {
            json.as_object()
                .and_then(|obj| obj.get("role"))
                .and_then(|val| val.as_str())
                .map(|s| s.to_string())
        })
        .collect();

    Ok(roles)
}

pub async fn find_by_id(id: i64) -> Result<Option<Model>> {
    let connection = get_connection()?;

    Entity::find()
        .filter(Column::Id.eq(id))
        .one(&connection)
        .await
        .map_err(|e| anyhow!("Error finding user role: {:?}", e))
}
